// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/loader/resource_loader.h"

#include <utility>

#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/profiler/scoped_tracker.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache_interceptor.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/loader/detachable_resource_handler.h"
#include "content/browser/loader/resource_handler.h"
#include "content/browser/loader/resource_loader_delegate.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/browser/service_worker/service_worker_request_handler.h"
#include "content/browser/service_worker/service_worker_response_info.h"
#include "content/browser/ssl/ssl_client_auth_handler.h"
#include "content/browser/ssl/ssl_manager.h"
#include "content/public/browser/resource_dispatcher_host_login_delegate.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/resource_response.h"
#include "content/public/common/resource_type.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_platform_key.h"
#include "net/ssl/ssl_private_key.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_status.h"

using base::TimeDelta;
using base::TimeTicks;

namespace content {
namespace {

    void PopulateResourceResponse(ResourceRequestInfoImpl* info,
        net::URLRequest* request,
        ResourceResponse* response)
    {
        response->head.request_time = request->request_time();
        response->head.response_time = request->response_time();
        response->head.headers = request->response_headers();
        request->GetCharset(&response->head.charset);
        response->head.content_length = request->GetExpectedContentSize();
        request->GetMimeType(&response->head.mime_type);
        net::HttpResponseInfo response_info = request->response_info();
        response->head.was_fetched_via_spdy = response_info.was_fetched_via_spdy;
        response->head.was_alpn_negotiated = response_info.was_alpn_negotiated;
        response->head.alpn_negotiated_protocol = response_info.alpn_negotiated_protocol;
        response->head.connection_info = response_info.connection_info;
        response->head.socket_address = response_info.socket_address;
        const content::ResourceRequestInfo* request_info = content::ResourceRequestInfo::ForRequest(request);
        if (request_info)
            response->head.previews_state = request_info->GetPreviewsState();

        response->head.effective_connection_type = net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;

        if (info->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME) {
            DCHECK(info->IsMainFrame());
            net::NetworkQualityEstimator* estimator = request->context()->network_quality_estimator();
            if (estimator) {
                response->head.effective_connection_type = estimator->GetEffectiveConnectionType();
            }
        }

        const ServiceWorkerResponseInfo* service_worker_info = ServiceWorkerResponseInfo::ForRequest(request);
        if (service_worker_info)
            service_worker_info->GetExtraResponseInfo(&response->head);
        AppCacheInterceptor::GetExtraResponseInfo(
            request, &response->head.appcache_id,
            &response->head.appcache_manifest_url);
        if (info->is_load_timing_enabled())
            request->GetLoadTimingInfo(&response->head.load_timing);

        if (request->ssl_info().cert.get()) {
            response->head.has_major_certificate_errors = net::IsCertStatusError(request->ssl_info().cert_status) && !net::IsCertStatusMinorError(request->ssl_info().cert_status);
            if (info->ShouldReportRawHeaders()) {
                // Only pass these members when the network panel of the DevTools is open,
                // i.e. ShouldReportRawHeaders() is set. These data are used to populate
                // the requests in the security panel too.
                response->head.cert_status = request->ssl_info().cert_status;
                response->head.ssl_connection_status = request->ssl_info().connection_status;
                response->head.ssl_key_exchange_group = request->ssl_info().key_exchange_group;
                response->head.signed_certificate_timestamps = request->ssl_info().signed_certificate_timestamps;
                std::string encoded;
                bool rv = net::X509Certificate::GetDEREncoded(
                    request->ssl_info().cert->os_cert_handle(), &encoded);
                DCHECK(rv);
                response->head.certificate.push_back(encoded);
                for (auto& cert :
                    request->ssl_info().cert->GetIntermediateCertificates()) {
                    rv = net::X509Certificate::GetDEREncoded(cert, &encoded);
                    DCHECK(rv);
                    response->head.certificate.push_back(encoded);
                }
            }
        } else {
            // We should not have any SSL state.
            DCHECK(!request->ssl_info().cert_status);
            DCHECK_EQ(request->ssl_info().security_bits, -1);
            DCHECK_EQ(request->ssl_info().key_exchange_group, 0);
            DCHECK(!request->ssl_info().connection_status);
        }
    }

} // namespace

ResourceLoader::ResourceLoader(std::unique_ptr<net::URLRequest> request,
    std::unique_ptr<ResourceHandler> handler,
    ResourceLoaderDelegate* delegate)
    : deferred_stage_(DEFERRED_NONE)
    , request_(std::move(request))
    , handler_(std::move(handler))
    , delegate_(delegate)
    , is_transferring_(false)
    , times_cancelled_before_request_start_(0)
    , started_request_(false)
    , times_cancelled_after_request_start_(0)
    , weak_ptr_factory_(this)
{
    request_->set_delegate(this);
    handler_->SetController(this);
}

ResourceLoader::~ResourceLoader()
{
    if (login_delegate_.get())
        login_delegate_->OnRequestCancelled();
    ssl_client_auth_handler_.reset();

    // Run ResourceHandler destructor before we tear-down the rest of our state
    // as the ResourceHandler may want to inspect the URLRequest and other state.
    handler_.reset();
}

void ResourceLoader::StartRequest()
{
    // Give the handler a chance to delay the URLRequest from being started.
    bool defer_start = false;
    if (!handler_->OnWillStart(request_->url(), &defer_start)) {
        Cancel();
        return;
    }

    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::StartRequest", this,
        TRACE_EVENT_FLAG_FLOW_OUT);
    if (defer_start) {
        deferred_stage_ = DEFERRED_START;
    } else {
        StartRequestInternal();
    }
}

void ResourceLoader::CancelRequest(bool from_renderer)
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CancelRequest", this,
        TRACE_EVENT_FLAG_FLOW_IN);
    CancelRequestInternal(net::ERR_ABORTED, from_renderer);
}

void ResourceLoader::CancelAndIgnore()
{
    ResourceRequestInfoImpl* info = GetRequestInfo();
    info->set_was_ignored_by_handler(true);
    CancelRequest(false);
}

void ResourceLoader::CancelWithError(int error_code)
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CancelWithError", this,
        TRACE_EVENT_FLAG_FLOW_IN);
    CancelRequestInternal(error_code, false);
}

void ResourceLoader::MarkAsTransferring(
    const base::Closure& on_transfer_complete_callback)
{
    CHECK(IsResourceTypeFrame(GetRequestInfo()->GetResourceType()))
        << "Can only transfer for navigations";
    is_transferring_ = true;
    on_transfer_complete_callback_ = on_transfer_complete_callback;

    int child_id = GetRequestInfo()->GetChildID();
    AppCacheInterceptor::PrepareForCrossSiteTransfer(request(), child_id);
    ServiceWorkerRequestHandler* handler = ServiceWorkerRequestHandler::GetHandler(request());
    if (handler)
        handler->PrepareForCrossSiteTransfer(child_id);
}

void ResourceLoader::CompleteTransfer()
{
    // Although NavigationResourceThrottle defers at WillProcessResponse
    // (DEFERRED_READ), it may be seeing a replay of events via
    // MimeTypeResourceHandler, and so the request itself is actually deferred at
    // a later read stage.
    DCHECK(DEFERRED_READ == deferred_stage_ || DEFERRED_RESPONSE_COMPLETE == deferred_stage_);
    DCHECK(is_transferring_);
    DCHECK(!on_transfer_complete_callback_.is_null());

    // In some cases, a process transfer doesn't really happen and the
    // request is resumed in the original process. Real transfers to a new process
    // are completed via ResourceDispatcherHostImpl::UpdateRequestForTransfer.
    int child_id = GetRequestInfo()->GetChildID();
    AppCacheInterceptor::MaybeCompleteCrossSiteTransferInOldProcess(
        request(), child_id);
    ServiceWorkerRequestHandler* handler = ServiceWorkerRequestHandler::GetHandler(request());
    if (handler)
        handler->MaybeCompleteCrossSiteTransferInOldProcess(child_id);

    is_transferring_ = false;
    base::ResetAndReturn(&on_transfer_complete_callback_).Run();
}

ResourceRequestInfoImpl* ResourceLoader::GetRequestInfo()
{
    return ResourceRequestInfoImpl::ForRequest(request_.get());
}

void ResourceLoader::ClearLoginDelegate()
{
    login_delegate_ = NULL;
}

void ResourceLoader::OnReceivedRedirect(net::URLRequest* unused,
    const net::RedirectInfo& redirect_info,
    bool* defer)
{
    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
        "ResourceLoader::OnReceivedRedirect");
    DCHECK_EQ(request_.get(), unused);

    DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec();
    DCHECK(request_->status().is_success());

    ResourceRequestInfoImpl* info = GetRequestInfo();

    // With PlzNavigate for frame navigations this check is done in the
    // NavigationRequest::OnReceivedRedirect() function.
    bool check_handled_elsewhere = IsBrowserSideNavigationEnabled() && IsResourceTypeFrame(info->GetResourceType());

    if (!check_handled_elsewhere) {
        if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(
                info->GetChildID(), redirect_info.new_url)) {
            DVLOG(1) << "Denied unauthorized request for "
                     << redirect_info.new_url.possibly_invalid_spec();

            // Tell the renderer that this request was disallowed.
            Cancel();
            return;
        }
    }

    scoped_refptr<ResourceResponse> response = new ResourceResponse();
    PopulateResourceResponse(info, request_.get(), response.get());
    delegate_->DidReceiveRedirect(this, redirect_info.new_url, response.get());
    if (!handler_->OnRequestRedirected(redirect_info, response.get(), defer)) {
        Cancel();
    } else if (*defer) {
        deferred_stage_ = DEFERRED_REDIRECT; // Follow redirect when resumed.
        DCHECK(deferred_redirect_url_.is_empty());
        deferred_redirect_url_ = redirect_info.new_url;
    } else if (delegate_->HandleExternalProtocol(this, redirect_info.new_url)) {
        // The request is complete so we can remove it.
        CancelAndIgnore();
        return;
    }
}

void ResourceLoader::OnAuthRequired(net::URLRequest* unused,
    net::AuthChallengeInfo* auth_info)
{
    DCHECK_EQ(request_.get(), unused);

    ResourceRequestInfoImpl* info = GetRequestInfo();
    if (info->do_not_prompt_for_login()) {
        request_->CancelAuth();
        return;
    }

    // Create a login dialog on the UI thread to get authentication data, or pull
    // from cache and continue on the IO thread.

    DCHECK(!login_delegate_.get())
        << "OnAuthRequired called with login_delegate pending";
    login_delegate_ = delegate_->CreateLoginDelegate(this, auth_info);
    if (!login_delegate_.get())
        request_->CancelAuth();
}

void ResourceLoader::OnCertificateRequested(
    net::URLRequest* unused,
    net::SSLCertRequestInfo* cert_info)
{
    DCHECK_EQ(request_.get(), unused);

    if (request_->load_flags() & net::LOAD_PREFETCH) {
        request_->Cancel();
        return;
    }

    DCHECK(!ssl_client_auth_handler_)
        << "OnCertificateRequested called with ssl_client_auth_handler pending";
    ssl_client_auth_handler_.reset(new SSLClientAuthHandler(
        delegate_->CreateClientCertStore(this), request_.get(), cert_info, this));
    ssl_client_auth_handler_->SelectCertificate();
}

void ResourceLoader::OnSSLCertificateError(net::URLRequest* request,
    const net::SSLInfo& ssl_info,
    bool fatal)
{
    ResourceRequestInfoImpl* info = GetRequestInfo();

    SSLManager::OnSSLCertificateError(
        weak_ptr_factory_.GetWeakPtr(), info->GetResourceType(), request_->url(),
        info->GetWebContentsGetterForRequest(), ssl_info, fatal);
}

void ResourceLoader::OnResponseStarted(net::URLRequest* unused)
{
    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
        "ResourceLoader::OnResponseStarted");
    DCHECK_EQ(request_.get(), unused);

    DVLOG(1) << "OnResponseStarted: " << request_->url().spec();

    if (!request_->status().is_success()) {
        ResponseCompleted();
        return;
    }

    CompleteResponseStarted();

    // If the handler deferred the request, it will resume the request later. If
    // the request was cancelled, the request will call back into |this| with a
    // bogus read completed error.
    if (is_deferred() || !request_->status().is_success())
        return;

    ReadMore(false); // Read the first chunk.
}

void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read)
{
    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
        "ResourceLoader::OnReadCompleted");
    DCHECK_EQ(request_.get(), unused);
    DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\""
             << " bytes_read = " << bytes_read;

    // bytes_read == -1 always implies an error.
    if (bytes_read == -1 || !request_->status().is_success()) {
        ResponseCompleted();
        return;
    }

    CompleteRead(bytes_read);

    // If the handler cancelled or deferred the request, do not continue
    // processing the read. If canceled, either the request will call into |this|
    // with a bogus read error, or, if the request was completed, a task posted
    // from ResourceLoader::CancelREquestInternal will run OnResponseCompleted.
    if (is_deferred() || !request_->status().is_success())
        return;

    if (bytes_read > 0) {
        ReadMore(true); // Read the next chunk.
    } else {
        // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
        tracked_objects::ScopedTracker tracking_profile(
            FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 ResponseCompleted()"));

        // URLRequest reported an EOF. Call ResponseCompleted.
        DCHECK_EQ(0, bytes_read);
        ResponseCompleted();
    }
}

void ResourceLoader::CancelSSLRequest(int error,
    const net::SSLInfo* ssl_info)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    // The request can be NULL if it was cancelled by the renderer (as the
    // request of the user navigating to a new page from the location bar).
    if (!request_->is_pending())
        return;
    DVLOG(1) << "CancelSSLRequest() url: " << request_->url().spec();

    if (ssl_info) {
        request_->CancelWithSSLError(error, *ssl_info);
    } else {
        request_->CancelWithError(error);
    }
}

void ResourceLoader::ContinueSSLRequest()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    DVLOG(1) << "ContinueSSLRequest() url: " << request_->url().spec();

    request_->ContinueDespiteLastError();
}

void ResourceLoader::ContinueWithCertificate(net::X509Certificate* cert)
{
    DCHECK(ssl_client_auth_handler_);
    ssl_client_auth_handler_.reset();
    if (!cert) {
        request_->ContinueWithCertificate(nullptr, nullptr);
        return;
    }
    scoped_refptr<net::SSLPrivateKey> private_key = net::FetchClientCertPrivateKey(cert);
    request_->ContinueWithCertificate(cert, private_key.get());
}

void ResourceLoader::CancelCertificateSelection()
{
    DCHECK(ssl_client_auth_handler_);
    ssl_client_auth_handler_.reset();
    request_->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
}

void ResourceLoader::Resume()
{
    DCHECK(!is_transferring_);

    DeferredStage stage = deferred_stage_;
    deferred_stage_ = DEFERRED_NONE;
    switch (stage) {
    case DEFERRED_NONE:
        NOTREACHED();
        break;
    case DEFERRED_START:
        StartRequestInternal();
        break;
    case DEFERRED_REDIRECT:
        FollowDeferredRedirectInternal();
        break;
    case DEFERRED_READ:
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&ResourceLoader::ResumeReading, weak_ptr_factory_.GetWeakPtr()));
        break;
    case DEFERRED_RESPONSE_COMPLETE:
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&ResourceLoader::ResponseCompleted, weak_ptr_factory_.GetWeakPtr()));
        break;
    case DEFERRED_FINISH:
        // Delay self-destruction since we don't know how we were reached.
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&ResourceLoader::CallDidFinishLoading, weak_ptr_factory_.GetWeakPtr()));
        break;
    }
}

void ResourceLoader::Cancel()
{
    CancelRequest(false);
}

void ResourceLoader::StartRequestInternal()
{
    DCHECK(!request_->is_pending());

    // Note: at this point any possible deferred start actions are already over.

    if (!request_->status().is_success()) {
        return;
    }

    if (delegate_->HandleExternalProtocol(this, request_->url())) {
        CancelAndIgnore();
        return;
    }

    started_request_ = true;
    request_->Start();

    delegate_->DidStartRequest(this);
}

void ResourceLoader::CancelRequestInternal(int error, bool from_renderer)
{
    DVLOG(1) << "CancelRequestInternal: " << request_->url().spec();

    ResourceRequestInfoImpl* info = GetRequestInfo();

    // WebKit will send us a cancel for downloads since it no longer handles
    // them.  In this case, ignore the cancel since we handle downloads in the
    // browser.
    if (from_renderer && (info->IsDownload() || info->is_stream()))
        return;

    if (from_renderer && info->detachable_handler()) {
        // TODO(davidben): Fix Blink handling of prefetches so they are not
        // cancelled on navigate away and end up in the local cache.
        info->detachable_handler()->Detach();
        return;
    }

    // TODO(darin): Perhaps we should really be looking to see if the status is
    // IO_PENDING?
    bool was_pending = request_->is_pending();

    if (login_delegate_.get()) {
        login_delegate_->OnRequestCancelled();
        login_delegate_ = NULL;
    }
    ssl_client_auth_handler_.reset();

    if (!started_request_) {
        times_cancelled_before_request_start_++;
    } else {
        times_cancelled_after_request_start_++;
    }

    request_->CancelWithError(error);

    if (!was_pending) {
        // If the request isn't in flight, then we won't get an asynchronous
        // notification from the request, so we have to signal ourselves to finish
        // this request.
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&ResourceLoader::ResponseCompleted, weak_ptr_factory_.GetWeakPtr()));
    }
}

void ResourceLoader::FollowDeferredRedirectInternal()
{
    DCHECK(!deferred_redirect_url_.is_empty());
    GURL redirect_url = deferred_redirect_url_;
    deferred_redirect_url_ = GURL();
    if (delegate_->HandleExternalProtocol(this, redirect_url)) {
        CancelAndIgnore();
    } else {
        request_->FollowDeferredRedirect();
    }
}

void ResourceLoader::CompleteResponseStarted()
{
    ResourceRequestInfoImpl* info = GetRequestInfo();
    scoped_refptr<ResourceResponse> response = new ResourceResponse();
    PopulateResourceResponse(info, request_.get(), response.get());

    delegate_->DidReceiveResponse(this);

    // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseStarted()"));

    bool defer = false;
    if (!handler_->OnResponseStarted(response.get(), &defer)) {
        Cancel();
    } else if (defer) {
        read_deferral_start_time_ = base::TimeTicks::Now();
        deferred_stage_ = DEFERRED_READ; // Read first chunk when resumed.
    }
}

void ResourceLoader::ReadMore(bool is_continuation)
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::ReadMore", this,
        TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
    DCHECK(!is_deferred());

    // Make sure we track the buffer in at least one place.  This ensures it gets
    // deleted even in the case the request has already finished its job and
    // doesn't use the buffer.
    scoped_refptr<net::IOBuffer> buf;
    int buf_size;
    {
        // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
        tracked_objects::ScopedTracker tracking_profile2(
            FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnWillRead()"));

        if (!handler_->OnWillRead(&buf, &buf_size, -1)) {
            // Cancel the request, which will then call back into |this| to inform it
            // of a "read error".
            Cancel();
            return;
        }
    }

    DCHECK(buf.get());
    DCHECK(buf_size > 0);

    int result = request_->Read(buf.get(), buf_size);

    if (result == net::ERR_IO_PENDING)
        return;

    if (!is_continuation || result <= 0) {
        OnReadCompleted(request_.get(), result);
    } else {
        // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
        // thread in case the URLRequest can provide data synchronously.
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE,
            base::Bind(&ResourceLoader::OnReadCompleted,
                weak_ptr_factory_.GetWeakPtr(), request_.get(), result));
    }
}

void ResourceLoader::ResumeReading()
{
    DCHECK(!is_deferred());

    if (!read_deferral_start_time_.is_null()) {
        UMA_HISTOGRAM_TIMES("Net.ResourceLoader.ReadDeferral",
            base::TimeTicks::Now() - read_deferral_start_time_);
        read_deferral_start_time_ = base::TimeTicks();
    }
    if (request_->status().is_success()) {
        ReadMore(false); // Read the next chunk (OK to complete synchronously).
    } else {
        ResponseCompleted();
    }
}

void ResourceLoader::CompleteRead(int bytes_read)
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CompleteRead", this,
        TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);

    DCHECK(bytes_read >= 0);
    DCHECK(request_->status().is_success());

    // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
    tracked_objects::ScopedTracker tracking_profile(
        FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnReadCompleted()"));

    bool defer = false;
    if (!handler_->OnReadCompleted(bytes_read, &defer)) {
        Cancel();
    } else if (defer) {
        deferred_stage_ = bytes_read > 0 ? DEFERRED_READ : DEFERRED_RESPONSE_COMPLETE;
    }

    // Note: the request may still have been cancelled while OnReadCompleted
    // returns true if OnReadCompleted caused request to get cancelled
    // out-of-band. (In AwResourceDispatcherHostDelegate::DownloadStarting, for
    // instance.)
}

void ResourceLoader::ResponseCompleted()
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::ResponseCompleted", this,
        TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);

    DVLOG(1) << "ResponseCompleted: " << request_->url().spec();
    RecordHistograms();

    bool defer = false;
    {
        // TODO(darin): Remove ScopedTracker below once crbug.com/475761 is fixed.
        tracked_objects::ScopedTracker tracking_profile(
            FROM_HERE_WITH_EXPLICIT_FUNCTION("475761 OnResponseCompleted()"));

        handler_->OnResponseCompleted(request_->status(), &defer);
    }
    if (defer) {
        // The handler is not ready to die yet.  We will call DidFinishLoading when
        // we resume.
        deferred_stage_ = DEFERRED_FINISH;
    } else {
        // This will result in our destruction.
        CallDidFinishLoading();
    }
}

void ResourceLoader::CallDidFinishLoading()
{
    TRACE_EVENT_WITH_FLOW0("loading", "ResourceLoader::CallDidFinishLoading",
        this, TRACE_EVENT_FLAG_FLOW_IN);
    delegate_->DidFinishLoading(this);
}

void ResourceLoader::RecordHistograms()
{
    ResourceRequestInfoImpl* info = GetRequestInfo();
    if (request_->response_info().network_accessed) {
        if (info->GetResourceType() == RESOURCE_TYPE_MAIN_FRAME) {
            UMA_HISTOGRAM_ENUMERATION("Net.HttpResponseInfo.ConnectionInfo.MainFrame",
                request_->response_info().connection_info,
                net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS);
        } else {
            UMA_HISTOGRAM_ENUMERATION(
                "Net.HttpResponseInfo.ConnectionInfo.SubResource",
                request_->response_info().connection_info,
                net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS);
        }
    }

    if (request_->load_flags() & net::LOAD_PREFETCH) {
        // Note that RESOURCE_TYPE_PREFETCH requests are a subset of
        // net::LOAD_PREFETCH requests. In the histograms below, "Prefetch" means
        // RESOURCE_TYPE_PREFETCH and "LoadPrefetch" means net::LOAD_PREFETCH.
        bool is_resource_type_prefetch = info->GetResourceType() == RESOURCE_TYPE_PREFETCH;
        PrefetchStatus prefetch_status = STATUS_UNDEFINED;
        TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time();

        switch (request_->status().status()) {
        case net::URLRequestStatus::SUCCESS:
            if (request_->was_cached()) {
                prefetch_status = request_->response_info().unused_since_prefetch
                    ? STATUS_SUCCESS_ALREADY_PREFETCHED
                    : STATUS_SUCCESS_FROM_CACHE;
                if (is_resource_type_prefetch) {
                    UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromCache",
                        total_time);
                }
            } else {
                prefetch_status = STATUS_SUCCESS_FROM_NETWORK;
                if (is_resource_type_prefetch) {
                    UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentPrefetchingFromNetwork",
                        total_time);
                }
            }
            break;
        case net::URLRequestStatus::CANCELED:
            prefetch_status = STATUS_CANCELED;
            if (is_resource_type_prefetch)
                UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeBeforeCancel", total_time);
            break;
        case net::URLRequestStatus::IO_PENDING:
        case net::URLRequestStatus::FAILED:
            prefetch_status = STATUS_UNDEFINED;
            break;
        }

        UMA_HISTOGRAM_ENUMERATION("Net.LoadPrefetch.Pattern", prefetch_status,
            STATUS_MAX);
        if (is_resource_type_prefetch) {
            UMA_HISTOGRAM_ENUMERATION("Net.Prefetch.Pattern", prefetch_status,
                STATUS_MAX);
        }
    } else if (request_->response_info().unused_since_prefetch) {
        TimeDelta total_time = base::TimeTicks::Now() - request_->creation_time();
        UMA_HISTOGRAM_TIMES("Net.Prefetch.TimeSpentOnPrefetchHit", total_time);
    }
}

} // namespace content
